// 创建 WxRequest 类
// 通过类的方式来进行封装，会让代码更加具有复用性
// 也可以方便添加新的属性和方法

class WxRequest {
  // 定义实例属性，用来设置默认请求参数===========================================================================
  defaults = {
    baseURL: '', // 请求基准地址
    url: '', // 接口的请求路径
    data: null, // 请求参数
    method: 'GET', // 默认的请求方法
    // 请求头
    header: {
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000, // 默认的超时时长，小程序默认的超时时长是 1 分钟
    isLoading: true // 控制是否使用默认的 loading，默认值是 true 表示使用默认的 loading
    //但是在实际开发中，有的接口可能不需要显示 `loading` 效果，或者开发者希望自己来控制 `loading` 的样式与交互，那么就需要关闭默认 `loading` 效果。
    //这时候我们就需要一个开关来控制 `loading` 显示。
  }

  // 定义拦截器对象===============================================================================================
  // 需要包含请求拦截器以及响应拦截器，方便在请求之前以及响应以后时进行逻辑处理
  //为了方便统一处理请求参数以及服务器响应结果，为 `WxRequest` 添加拦截器功能，拦截器包括 **请求拦截器** 和 **响应拦截器**
  interceptors = {
    // 请求拦截器
    // 在请求发送之前，对请求参数进行新增或者修改
    //请求拦截器本质上是在请求之前调用的函数，用来对请求参数进行新增和修改
    request: (config) => config,

    // 响应拦截器
    // 在服务器响应数据以后，对服务器响应的数据进行逻辑处理
    //响应拦截器本质上是在响应之后调用的函数，用来对响应数据做点什么
    response: (response) => response
  }

  // 定义数组队列=====================================================================================================
  // 初始值需要是一个空数组，用来存储请求队列、存储请求标识
  //1. 每次请求都会执行 `wx.showLoading()`，但是页面中只会显示一个，后面的 `loading`会将前面的覆盖
  //2. 同时发起多次请求，只要有一个请求成功响应就会调用 `wx.hideLoading`，导致其他请求还没完成，也不会 `loading`
  //3. 请求过快 或 一个请求在另一个请求后立即触发，这时候会出现 `loading` 闪烁问题
  //我们通过 **队列** 的方式解决这三个问题
  //初始值需要时一个空数组，用来存储请求队列，存储请求标识
  queue = []

  // 用于创建和初始化类的属性以及方法=================================================================================
  // 在实例化时传入的参数，会被 constructor 形参进行接收
  constructor(params = {}) {
    // 通过 Object.assign 方法合并请求参数
    // 注意：需要传入的参数，覆盖默认的参数，因此传入的参数需要放到最后
    this.defaults = Object.assign({}, this.defaults, params)
  }

  // request 实例方法接收一个对象类型的参数===========================================================================
  // 属性值和 wx.request 方法调用时传递的参数保持一致
  request(options) {
    // 如果有新的请求，就清除上一次的定时器
    this.timerId && clearTimeout(this.timerId)

    // 注意：需要先合并完整的请求地址 (baseURL + url)=====================================================================
    // https://gmall-prod.atguigu.cn/mall-api/index/findBanner
    options.url = this.defaults.baseURL + options.url

    // 合并请求参数,展开运算符合并，后面覆盖前面
    options = { ...this.defaults, ...options }

    // 在请求发送之前，添加 loading 效果=======================================================================
    //在封装时添加 `loading` 效果，从而提高用户使用体验
    // wx.showLoading()
    //做判断，要求&& options.method !== 'UPLOAD'
    if (options.isLoading && options.method !== 'UPLOAD') {
      // 判断 queue 队列是否为空，如果是空，就显示 loading
      // 如果不是空，就不显示 loading，不调用 wx.showLoading()
      this.queue.length === 0 && wx.showLoading()

      // 然后立即向 queue 数组队列中添加请求标识
      // 每个标识代表是一个请求，标识是自定义的
      this.queue.push('request')
    }

    // 在请求发送之前，调用请求拦截器，新增和修改请求参数
    options = this.interceptors.request(options)

    //==========================================================================================
    // 需要使用 Promise 封装 wx.request，处理异步请求
    return new Promise((resolve, reject) => {
      if (options.method === 'UPLOAD') {
        //如果是上传调用wx.uploadFile这个api==========================
        wx.uploadFile({
          ...options,

          success: (res) => {
            // 需要将服务器返回的 JSON 字符串 通过 JSON.parse 转成对象
            res.data = JSON.parse(res.data)

            // 合并参数，Object.assign(）
            //接受参数const mergeRes
            const mergeRes = Object.assign({}, res, {
              config: options,
              isSuccess: true
            })
            //把mergeRes参数传递给响应器
            resolve(this.interceptors.response(mergeRes))
          },

          fail: (err) => {
            // 合并参数，Object.assign(）
            //接受参数const mergeErr
            const mergeErr = Object.assign({}, err, {
              config: options,
              isSuccess: false
            })
            //把mergeErr参数传递给响应器
            reject(this.interceptors.response(mergeErr))
          }
        })
      } else {
        //否则调用wx.request这个api==========================
        wx.request({
          ...options,

          // 当接口调用成功时会触发 success 回调函数=============================================
          success: (res) => {
            // 不管是成功响应还是失败响应，都需要调用响应拦截器
            // 响应拦截器需要接收服务器响应的数据，然后对数据进行逻辑处理，处理好以后进行返回
            // 然后在通过 resolve 将返回的数据抛出去

            // 在给响应拦截器传递参数时，需要将请求参数也一起传递
            // 方便进行代码的调试或者进行其他逻辑处理，需要先合并参数
            // 然后将合并的参数传递给响应拦截器

            //======================================================
            // 不管是请求失败还是请求成功，都已经将响应的数据传递给了响应拦截器
            // 这时候在合并参数的时候，追加一个属性：isSuccess
            // 如果属性值为 true，说明执行了 success 回调函数
            // 如果属性值为 false，说明执行了 fail 回调函数
            //======================================================

            //mergeRes合并以后的数据，Object.assign，合并到空对象里
            //把合并后的数据传递给响应拦截器，调用this.interceptors.response(mergeRes)
            const mergeRes = Object.assign({}, res, {
              config: options,
              isSuccess: true
            })
            resolve(this.interceptors.response(mergeRes))
          },

          // 当接口调用失败时会触发 fail 回调函数=====================================
          fail: (err) => {
            // 不管是成功响应还是失败响应，都需要调用响应拦截器
            //mergeErr合并以后的数据，Object.assign，合并到空对象里
            //把合并后的数据传递给响应拦截器，调用this.interceptors.response(mergeErr)
            const mergeErr = Object.assign({}, err, {
              config: options,
              isSuccess: false
            })
            reject(this.interceptors.response(mergeErr))
          },

          // 接口调用结束的回调函数（调用成功、失败都会执行）=================================
          complete: () => {
            if (options.isLoading) {
              // 在每一个请求结束以后，都会执行 complete 回调函数
              // 每次从 queue 队列中删除一个标识
              this.queue.pop()

              //为了解决网络请求过快产生`loading` 闪烁问题，可以使用定时器来做判断即可
              this.queue.length === 0 && this.queue.push('request')

              //添加一个定时器
              this.timerId = setTimeout(() => {
                this.queue.pop()

                // 在删除标识以后，需要判断目前 queue 数组是否为空=====================================
                // 如果是空，说明并发请求完成了
                // 就需要隐藏 loading，要调用 wx.hideLoading()
                this.queue.length === 0 && wx.hideLoading()

                //Loading隐藏后清除定时器
                clearTimeout(this.timerId)
              }, 1)

              // 不管请求时成功还是失败，都需要隐藏 loading
              // wx.hideLoading()
            }
          }
        })
      }
    })
  }

  //这 4 个快捷方法，本质上其实还是调用 `request` 方法，我们只要在方法内部组织好参数，调用 `request` 发送请求即可
  // 封装 GET 实例方法===============================================================================================
  get(url, data = {}, config = {}) {
    // 需要调用 request 请求方法发送请求，只需要组织好参数，传递给 request 请求方法即可
    // 当调用 get 方法时，需要将 request 方法的返回值 return 出去
    return this.request(Object.assign({ url, data, method: 'GET' }, config))
  }

  // 封装 DELETE 实例方法========================================================================================
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'DELETE' }, config))
  }

  // 封装 POST 实例方法==========================================================================================
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'POST' }, config))
  }

  // 封装 PUT 实例方法=============================================================================================
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'PUT' }, config))
  }

  // 用来处理并发请求==============================================================================================
  //通过演示就能知道，通过并发请求，通过promise.all发并发请求，就可以提升整个页面的渲染速度，所以我们需要在wxrequest这个类里面封装
  //一个all方法，这个all实例方法就是用来处理并发请求，注意一下，all方法的封装并不是必须的，你可以直接使用promise.all发并发请求，只不过
  //是习惯对all进行封装，因为这样在调用接口api时，都通过实例的方式进行调用，风格会趋向于统一
  //前端并发请求是指在前端页面同时向后端发起多个请求的情况。当一个页面需要请求多个接口获取数据时，为了提高页面的加载速度和用户体验，可以同时发起多个请求，这些请求之间就是并发的关系。
  all(...promise) {
    // 通过展开运算符接收传递的参数
    // 那么展开运算符会将传入的参数转成数组
    // console.log(promise)

    return Promise.all(promise)
  }

  //upload 实例方法，用来对 wx.uploadFile 进行封装===========================================================================
  /**
   * @description upload 实例方法，用来对 wx.uploadFile 进行封装
   * @param {*} url 文件的上传地址、接口地址
   * @param {*} filePath 要上传的文件资源路径
   * @param {*} name 文件对应的 key
   * @param {*} config 其他配置项
   */
  upload(url, filePath, name = 'file', config = {}) {
    return this.request(
      Object.assign(
        {
          url, //开发者服务器地址，接口地址
          filePath, //要上传的文件路径
          name, //文件对应的key，服务器需要根据key来获取文件的二进制信息
          method: 'UPLOAD'
        },
        config
      )
    )
  }
}

export default WxRequest
